package br.edu.utfpr.backuptools.BackupTools.model.dao.interfaces;

import java.util.List;
import java.util.Map;

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;

import br.edu.utfpr.backuptools.BackupTools.model.pojo.IBean;
import br.edu.utfpr.backuptools.BackupTools.model.util.hibernate.HibernateUtil;

/**
 * Classe abstrata a ser extendida pelos DaoImpls.
 * 
 * 
 * @param <POJO extends IBean<?> o Bean/Pojo a ser persistido por este DAO.
 */
public abstract class AbstractDao<POJO extends IBean> implements Dao<POJO>
{
	private final Class<POJO> clazz;
	/**
	 * Construtor que "implicita" a session como "HibernateUtil.currentSession()".
	 * 
	 * @param classe
	 */
	public AbstractDao(final Class<POJO> classe)
	{
		this.clazz = classe;
	}
	
	/**
	 * Salva um POJO.
	 * 
	 * @param pojo
	 */
	@SuppressWarnings("unchecked")
	public void save(POJO pojo)
	{
		Session s = HibernateUtil.getInstance().currentSession();
		HibernateUtil.getInstance().beginTransaction();
		// fazendo isso evito o fodendo erro de "an object with the same identifier bla bla bla in this session."
		// esse erro s� tem chance de ocorrer em um objeto que j� foi persistido (um nao persistido n�o tem identifier (capit�o obvio))
		if(pojo.getId() != null)
		{
			// o merge nao atualiza o objeto sozinho, então tenho que setar o objeto atualizado de volta nele.
			pojo = (POJO) s.merge(pojo);
		}
		else
		{
			// senao dou o save or update normal like a boss.
			s.saveOrUpdate(pojo);
		}
		s.flush(); 
		
		HibernateUtil.getInstance().commitTransaction();
	}
	
	/**
	 * Deleta uma POJO.
	 * 
	 * @param pojo
	 */
	public void delete(POJO pojo)
	{
		Session s = HibernateUtil.getInstance().currentSession();
		HibernateUtil.getInstance().beginTransaction();
		s.delete(pojo);
		s.flush();
		HibernateUtil.getInstance().commitTransaction();
	}
	
	/**
	 * Carrega um DTO pelo ID e retorna.
	 */
	@SuppressWarnings("unchecked")
	@Override
	public POJO load(Long id)
	{
		Session s = HibernateUtil.getInstance().currentSession();
		HibernateUtil.getInstance().beginTransaction();
		if(id != null)
		{
			return (POJO) s.load(clazz, id);
		}
		return null;
	}
	
	/**
	 * Retorna todos os DTOs.
	 */
	@Override
	public List<POJO> list()
	{
		return busca();
	}
	
	/**
	 * Retorna o criteria para o Classe.
	 * 
	 * @return
	 */
	public Criteria getCriteria()
	{
		Session s = HibernateUtil.getInstance().currentSession();
		HibernateUtil.getInstance().beginTransaction();
		return s.createCriteria(this.clazz);
	}
	
	public Criteria getRowCountCriteria()
	{
		Criteria c = getCriteria();
		c.setProjection(Projections.rowCount());
		return c;
	}
	
	@SuppressWarnings("unchecked")
	public List<POJO> busca()
	{
		//monta a query e retorna a lista dos resultados
		return montarCriteria(null, null, null).list();
	}
	
	@SuppressWarnings("unchecked")
	public List<POJO> busca(Order order)
	{
		//monta a query e retorna a lista dos resultados
		return montarCriteria(null, null, order).list();
	}
	
	@SuppressWarnings("unchecked")
	public List<POJO> busca(List<Criterion> restricoes)
	{
		//monta a query e retorna a lista dos resultados
		return montarCriteria(restricoes, null, null).list();
	}
	
	@SuppressWarnings("unchecked")
	public List<POJO> busca(List<Criterion> restricoes, Order order)
	{
		//monta a query e retorna a lista dos resultados
		return montarCriteria(restricoes, null, order).list();
	}
	
	@SuppressWarnings("unchecked")
	public List<POJO> busca(List<Criterion> restricoes, Order order, int limit)
	{
		//monta a query e retorna a lista dos resultados
		return montarCriteria(restricoes, null, order, limit).list();
	}
	
	/**
	 * Monta a Criteria de busca com os criterios, alias e a ordem. <br>
	 * Se não tiver algum parametro por exemplo o alias, é só passar null
	 * 
	 * @param restricoes
	 *            criterios para a busca
	 * @param alias
	 *            CHAVE = nome do atributo no bean - VALOR = nome que vai ser dado ao alias
	 * @param order
	 *            campo ao que vai ser ordenado o resultado
	 * @return criteria
	 */
	public Criteria montarCriteria(List<Criterion> restricoes, Map<String, String> alias, Order order)
	{
		return montarCriteria(restricoes, alias, order, 0);
	}
	
	@SuppressWarnings("unchecked")
	public List<POJO> busca(List<Criterion> restricoes, Map<String, String> alias, Order order)
	{
		//monta a query e retorna a lista dos resultados
		return montarCriteria(restricoes, alias, order).list();
	}
	
	@SuppressWarnings("unchecked")
	public List<POJO> busca(List<Criterion> restricoes, Map<String, String> alias, Order order, Integer limit)
	{
		//monta a query e retorna a lista dos resultados
		return montarCriteria(restricoes, alias, order, limit).list();
	}
	
	@SuppressWarnings("unchecked")
	public List<POJO> busca(List<Criterion> restricoes, Map<String, String> alias)
	{
		//monta a query e retorna a lista dos resultados
		return montarCriteria(restricoes, alias, null).list();
	}
	
	/**
	 * Sobrecarga do metodo montarCriteria, passando o campo Limit, para limitar os resultados de acordo com a variavel limit.
	 * 
	 * @param restricoes
	 * @param alias
	 * @param order
	 * @param limit
	 * @return
	 */
	public Criteria montarCriteria(List<Criterion> restricoes, Map<String, String> alias, Order order, int limit)
	{
		
		Criteria c = getCriteria();
		
		if(alias != null)
		{
			//adiciona os alias passados a criteria
			for(String campo : alias.keySet())
			{
				c.createAlias(campo, alias.get(campo));
			}
		}
		
		if(restricoes != null)
		{
			//adiciona os criterios passados a criteria
			for(Criterion r : restricoes)
			{
				c.add(r);
			}
		}
		
		if(order != null)
		{
			c.addOrder(order);
		}
		
		if(limit > 0)
		{
			c.setMaxResults(limit);
		}
		return c;
	}
	
}
